﻿using System;
using System.Collections.Specialized;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Xpedite.Config;
using Xpedite.Extensions;
using Xpedite.Handlers;
using Xpedite.Resources;
using Xpedite.Resources.Builders;
using Xpedite.Resources.Combiners;
using Xpedite.Resources.Compressors;
using Xpedite.Resources.Definitions;
using Xpedite.Resources.Loaders;
using Xpedite.WebAbstraction;

/* Copyright (c) 2010 CBaxter
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
 * IN THE SOFTWARE. 
 */

namespace Xpedite.Test.Handlers
{
  [TestClass]
  public class MinificationHandlerTest
  {
    private readonly Mock<IResourceBuilderFactory> _builderFactory = new Mock<IResourceBuilderFactory>(MockBehavior.Strict);
    private readonly Mock<ICompressorFactory> _compressorFactory = new Mock<ICompressorFactory>(MockBehavior.Strict);
    private readonly Mock<IResourceContainer> _resourceContainer = new Mock<IResourceContainer>(MockBehavior.Strict);
    private readonly Mock<IResourceCache> _cache = new Mock<IResourceCache>(MockBehavior.Strict);
    private readonly Mock<IWebResponse> _response = new Mock<IWebResponse>(MockBehavior.Strict);
    private readonly Mock<IWebRequest> _request = new Mock<IWebRequest>(MockBehavior.Strict);
    private readonly Mock<IWebContext> _context = new Mock<IWebContext>(MockBehavior.Strict);
    private readonly Mock<IResource> _resource = new Mock<IResource>(MockBehavior.Strict);

    public MinificationHandlerTest()
    {
      var definitionArgs = new DefinitionArgs(
          _cache.Object,
          new Mock<IResourceUrlGenerator>(MockBehavior.Strict).Object,
          _builderFactory.Object,
          new Mock<ICombinerFactory>(MockBehavior.Strict).Object
        );

      _context.SetupGet(mock => mock[WebContextExtensions.DefinitionArgsKey]).Returns(definitionArgs);
      _context.SetupGet(mock => mock.Response).Returns(_response.Object);
      _context.SetupGet(mock => mock.Request).Returns(_request.Object);

      _request.SetupGet(mock => mock.ApplicationPath).Returns("/");
      _request.SetupGet(mock => mock.Url).Returns(new Uri("http://localhost/resource.url"));
      _request.Setup(mock => mock.MapPath("/resource.url")).Returns(String.Empty);
    }

    [TestMethod]
    public void TestProcessRequestWhereResourceFoundAndNotModified()
    {
      var lastModified = DateTime.Now;
      var capturedFactory = ExpectGetCachedResource(lastModified);
      var handler = new MinificationHandler(ConfigMode.On, _compressorFactory.Object);

      _request.SetupGet(mock => mock.Headers).Returns(new NameValueCollection { { WebRequestHeader.IfModifiedSince, lastModified.ToString(DateFormat.RFC1123) } });
      _response.Setup(mock => mock.NotModified());

      handler.ProcessRequest(_context.Object, ResourceType.Css);

      Assert.IsNotNull(capturedFactory.Value);
    }

    [TestMethod]
    public void TestProcessRequestWhereResourceFoundAndModified()
    {
      var lastModified = DateTime.Now;
      var compressor = new GZipCompressor();
      var capturedUrl = new CapturedValue<IResourceUrl>();
      var capturedFactory = ExpectGetCachedResource(lastModified);
      var builder = new Mock<IResourceBuilder>(MockBehavior.Strict);
      var handler = new MinificationHandler(ConfigMode.On, _compressorFactory.Object);

      _request.SetupGet(mock => mock.Headers).Returns(new NameValueCollection { { WebRequestHeader.IfModifiedSince, lastModified.Date.ToString(DateFormat.RFC1123) }, { WebRequestHeader.AcceptEncoding, "gzip, deflate" } });
      _compressorFactory.Setup(mock => mock.Create("gzip, deflate")).Returns(compressor);
      _response.Setup(mock => mock.SendResource(_resource.Object, compressor));

      handler.ProcessRequest(_context.Object, ResourceType.Css);

      //Verify Factory Create
      _builderFactory.Setup(mock => mock.Create(ResourceType.Css, It.Is(capturedUrl.Match))).Returns(builder.Object);
      builder.Setup(mock => mock.Build()).Returns(_resourceContainer.Object);
      capturedFactory.Value.Invoke();

      //Verify IResourceUrl Create
      Assert.AreEqual("/resource.url", capturedUrl.Value.VirtualPath); 
    }

    [TestMethod]
    public void TestProcessRequestWhereResourceFoundWithException()
    {
      var handler = new MinificationHandler(ConfigMode.On, _compressorFactory.Object);
      var ex = new Exception();

      _cache.Setup(mock => mock.GetOrCreateWithFileDependency(It.Is<IResourceUrl>(value => value.VirtualPath == "/resource.url"), It.IsAny<Func<IResourceContainer>>(), String.Empty)).Returns(_resourceContainer.Object);
      _resourceContainer.SetupGet(mock => mock.Resource).Throws(ex);
      _cache.Setup(mock => mock.Remove(It.Is<IResourceUrl>(value => value.VirtualPath == "/resource.url")));

      Expect.Exception(() => handler.ProcessRequest(_context.Object, ResourceType.Css), ex);
    }

    [TestMethod]
    public void TestProcessRequestWithMinificationModeOff()
    {
      var lastModified = DateTime.Now;
      var resourceLoaderFactory = new Mock<IResourceLoaderFactory>(MockBehavior.Strict);
      var handler = new MinificationHandler(ConfigMode.Off, _compressorFactory.Object, resourceLoaderFactory.Object);

      ExpectGetDebugResource(lastModified, resourceLoaderFactory);
      ExpectNotModified(lastModified);

      handler.ProcessRequest(_context.Object, ResourceType.Css);
    }

    [TestMethod]
    public void TestProcessRequestWithMinificationModeRemoteOnlyAndRequestLocal()
    {
      var lastModified = DateTime.Now;
      var resourceLoaderFactory = new Mock<IResourceLoaderFactory>(MockBehavior.Strict);
      var handler = new MinificationHandler(ConfigMode.RemoteOnly, _compressorFactory.Object, resourceLoaderFactory.Object);

      ExpectGetDebugResource(lastModified, resourceLoaderFactory);
      ExpectNotModified(lastModified);

      _request.SetupGet(mock => mock.IsLocal).Returns(true);

      handler.ProcessRequest(_context.Object, ResourceType.Css);
    }

    [TestMethod]
    public void TestProcessRequestWithMinificationModeRemoteOnlyAndRequestNotLocal()
    {
      var lastModified = DateTime.Now;
      var resourceLoaderFactory = new Mock<IResourceLoaderFactory>(MockBehavior.Strict);
      var handler = new MinificationHandler(ConfigMode.RemoteOnly, _compressorFactory.Object, resourceLoaderFactory.Object);

      ExpectGetCachedResource(lastModified);
      ExpectNotModified(lastModified);

      _request.SetupGet(mock => mock.IsLocal).Returns(false);

      handler.ProcessRequest(_context.Object, ResourceType.Css);
    }

    [TestMethod]
    public void TestProcessRequestWithMinificationModeOn()
    {
      var lastModified = DateTime.Now;
      var resourceLoaderFactory = new Mock<IResourceLoaderFactory>(MockBehavior.Strict);
      var handler = new MinificationHandler(ConfigMode.On, _compressorFactory.Object, resourceLoaderFactory.Object);

      ExpectGetCachedResource(lastModified);
      ExpectNotModified(lastModified);

      _request.SetupGet(mock => mock.IsLocal).Returns(false);

      handler.ProcessRequest(_context.Object, ResourceType.Css);
    }

    private static void ExpectGetDebugResource(DateTime lastModified, Mock<IResourceLoaderFactory> resourceLoaderFactory)
    {
      var resourceLoader = new Mock<IResourceLoader>(MockBehavior.Strict);

      resourceLoaderFactory.Setup(mock => mock.Create(It.IsAny<IResourceUrl>())).Returns(resourceLoader.Object);
      resourceLoader.Setup(mock => mock.LoadData()).Returns(new ResourceData { Bytes = Encoding.UTF8.GetBytes("Debug"), Encoding = Encoding.UTF8, LastModified = lastModified });
    }

    private void ExpectNotModified(DateTime lastModified)
    {
      _request.SetupGet(mock => mock.Headers).Returns(new NameValueCollection { { WebRequestHeader.IfModifiedSince, lastModified.ToString(DateFormat.RFC1123) } });
      _response.Setup(mock => mock.NotModified());
    }

    private CapturedValue<Func<IResourceContainer>> ExpectGetCachedResource(DateTime lastModified)
    {
      var capturedFactory = new CapturedValue<Func<IResourceContainer>>();

      _cache.Setup(mock => mock.GetOrCreateWithFileDependency(It.Is<IResourceUrl>(value => value.VirtualPath == "/resource.url"), It.Is(capturedFactory.Match), String.Empty)).Returns(_resourceContainer.Object);
      _resourceContainer.SetupGet(mock => mock.Resource).Returns(_resource.Object);
      _resource.SetupGet(mock => mock.LastModified).Returns(lastModified);

      return capturedFactory;
    }
  }
}
